塊 (編程)
来自维基百科,自由的百科全书
在電腦編程中,塊(block)或譯為程式區塊、代碼塊,是將原始碼組織在一起的詞法結構。塊構成自一個或多個聲明和語句。程式語言允許建立塊,包括嵌入其他塊之內的塊,就叫做塊結構程式語言。塊和子程式是結構化編程的基礎,結構化所強調的控制結構可以用塊來形成的。
在編程中塊的功能,是確使成組的語句被當作如同就是一個語句,限定在一個塊中聲明的對象如變數、過程和函式的詞法作用域,使得它們不衝突於在其他地方用到的同名者。在塊結構程式語言中,在塊外部的對象名字在塊內部是可見的,除非它們被聲明了相同名字的對象所遮掩。
歷史
塊結構的想法是在1950年代開發最初的Autocode期間發展出來的,並形式化於ALGOL 60報告中。ALGOL 58介入了「複合」(compound)語句的概念,它只與控制流程有關[1]。在「ALGOL 60報告」中,介入了塊和作用域的概念[2]。最終在「修訂報告」中,複合語句被定義為:包圍在語句括號begin
和end
之間的成序列的語句,形成一個複合語句。塊被定義為:成序列的聲明,跟隨著成序列的語句,並被包圍在begin
和end
之間,形成一個塊;所有聲明以這種方式出現在一個塊中,並只在這個塊中有效[3]。塊與複合語句的主要差異是不能從塊外跳轉到塊內的標籤[4]。
語法
塊在不同語言家族中使用不同的語法:
- ALGOL語言家族,ALGOL 60及其後繼者比如Simula,使用語句括號
begin
和end
來界定複合語句和塊。ALGOL 68成為了面向表達式程式語言,偏好使用與begin
和end
等價的圓括號(
和)
[5]。 - Lisp語言家族,Lisp 1.5使用具有語法關鍵字
prog
的S-表達式表示塊[9],而Maclisp和Scheme使用let
形式的S-表達式來表示塊[10],S-表達式是圓括號(
和)
包圍的字首表示法。 - Smalltalk語言家族,Smalltalk-80和Self使用方括號
[
和]
來界定塊。
此外,複合語句界定還可以採用:
建立控制結構,除了將所控制的語句序列,包圍入複合語句或匿名塊之外,還可以採用其他語法機制:
- 在ALGOL 68中,條件和迭代語句,使用塊首保留字的反寫保留字來終止,比如:
IF ~ THEN ~ ELIF ~ THEN ~ ELSE ~ FI
和FOR ~ FROM ~ TO ~ BY ~ WHILE ~ DO ~ OD
。繼承此風格的有:Dijkstra的守衛命令語言和Bourne的Bourne shell等。 - 一些結構化編程語言,如FORTRAN 77、Modula-2、Ada和Visual Basic等,對控制結構加結束關鍵字,比如Modula-2中的:
IF ~ THEN ~ ELSIF ~ THEN ~ ELSE ~ END
和FOR ~ TO ~ BY ~ DO ~ END
。
限制
受ALGOL影響的一些語言支援塊,但有著各自的限制:
基本語意
塊的語意是雙重的。首先,它向編程者提供了建立任意大和複雜的結構,並把它當作一個單元的一種途徑。其次,它確使編程者能限制變數的作用域,有時可以限制已經被聲明了的其他對象的作用域。
在早期語言比如FORTRAN和BASIC中,沒有語句塊或控制結構。直到1978年標準化FORTRAN 77之前,都沒有「塊狀IF
」語句,要實現按條件選擇,必須訴諸GOTO
語句。例如下述FORTRAN代碼片段,從雇員工資中分別扣除超出正稅閾值部分的稅款,和超出附加稅閾值部分的附加稅款:
C 语言:ANSI标准FORTRAN 66
C 初始化要计算的值
PAYSTX = .FALSE.
PAYSST = .FALSE.
TAX = 0.0
SUPTAX = 0.0
C 如果雇员挣钱小于等于正税阈值则跃过税款扣除
IF (WAGES .LE. TAXTHR) GOTO 10
PAYSTX = .TRUE.
TAX = (WAGES - TAXTHR) * BASCRT
10 CONTINUE
C 如果雇员挣钱小于等于附加税阈值则跃过附加税扣除
IF (WAGES .LE. SUPTHR) GOTO 20
PAYSST = .TRUE.
SUPTAX = (WAGES - SUPTHR) * SUPRAT
20 CONTINUE
TAXED = WAGES - TAX - SUPTAX
程式的邏輯結構不反映在代碼中,這裡的初始化的值,是後面的有關邏輯判斷為假時所應當設定的值。
塊允許編程者把一組語句當作一個單元。例如,在與上述FORTRAN代碼相對應的Pascal代碼片段:
{ 语言:Jensen与Wirth版标准Pascal }
if Wages > TaxThreshold then
begin
PaysTax := true;
Tax := (Wages - TaxThreshold) * TaxRate
end
else begin
PaysTax := false;
Tax := 0
end;
if Wages > SupertaxThreshold then
begin
PaysSupertax := true;
Supertax := (Wages - SupertaxThreshold) * SupertaxRate
end
else begin
PaysSupertax := false;
Supertax := 0
end;
Taxed := Wages - Tax - Supertax;
與上述FORTRAN代碼相比,上例中出現在初始化中的那些預設值,通過複合語句即不帶聲明的塊結構,被分別放置作出有關邏輯判斷的地方。使用塊結構,能明晰編程者的意圖,使代碼的結構更加密切反映出編程者的思考;再憑藉某種風格的縮排和駝峰式大小寫增進可讀性,可使代碼更加容易理解和修改。
在早期語言中,在次常式中變數的作用域遍及整個次常式。假想在一個Fortran次常式中,完成了與管理者有關的任務,這裡可能用到叫做IEMPNO
的一個整數變數,指示作為管理者的雇員的社會安全號碼(SSN);後來在這個次常式的維護工作中,又增加與下屬們有關的任務,此時編程者可能不經意間使用同名變數IEMPNO
,指示了作為這個管理者的下屬的雇員的SSN,這就會導致一個難於跟蹤的缺陷。
塊結構使得編程者能夠容易地將作用域控制到細微級別。例如完成有關雇員任務的Scheme代碼片段:
;; 语言:R5RS标准Scheme
(let ((empno (ssn-of employee-name)))
(when (is-manager? empno) ;; when已列入R7RS-small标准
(let ((employee-list (underlings-of empno)))
(display
;; format是SRFI-28和SRFI-48规定的字符串格式化过程
(format "~a has ~a employees working under him:~%"
employee-name (length employee-list)))
(for-each
(lambda (empno)
(display
(format "Name: ~a, role: ~a~%"
(name-of empno) (role-of empno))))
employee-list))))
這裡在外層通過繫結宏let
將管理者的SSN繫結到了局部變數empno
,在其形成的塊的作用域中列出管理者的雇員名字和他的下屬數目;隨後通過for-each
高階函式,將他所有下屬的SSN逐個繫結到匿名函式lambda
的形式參數empno
上,執行此匿名函式列出這個下屬的名字和角色;這個形式參數的作用域是此匿名函式的主體,它與其外層的局部變數,識別碼重名但不相互影響。在實踐中,出於清晰性的考慮,編程者更可能選取明顯不同的變數名字,但是即使名字選取存在重複,也難以在不經意間介入一個缺陷。在基於S-表達式的語言中,經常見到大量的巢狀圓括號,故而其代碼必須採用良好的縮排。
提升
在一些語言中,變數可以聲明為有函式作用域即使它位於函式的內嵌塊之中。例如在JavaScript中,變數應當總是在使用之前被聲明,它曾經允許賦值到未聲明變數,會為此建立為未聲明的全域變數,這在strict
模態下是個錯誤。以var
聲明的變數有函式作用域,而非以let
或const
聲明的變數可從屬的塊作用域。以var
聲明的變數會被提升(hoist),這意味著可以在這個函式的作用域內任何地方提及這個變數,即使還未觸及到它的聲明,從而可以將var
聲明視為被提舉(lift)到它所在函式的頂部或全域作用域。但是如果在其聲明之前訪問了一個變數,這個變數的值總是未指定的。
參見
參照
Wikiwand - on
Seamless Wikipedia browsing. On steroids.